
import datetime
import json
import math
from math import pi,sin,cos
import time,redis
import numpy as np
import pandas as pd
pool_redis= redis.ConnectionPool(host='localhost',port=6379,decode_responses=True)
mesg_redis=redis.Redis(connection_pool=pool_redis)


#围栏报警redis取围栏数据，和本次的数据进行匹配计算
def efence_compute(compute_dynamic,efence,owner):
	# print('efence')
	#围栏数据格式，{efence_name:{name:围栏1,alarm_kind:in/out/all,s_time:xxx,e_time:xxx,kind:circle/polygon/rec,ontent:[]，in_out_status:in/out，alarm_status:[alarm_name,s_time,e_time]}，efence_name:{}}
	device_id=compute_dynamic['device_id']
	alarm_loca=compute_dynamic['location']
	lng=compute_dynamic['lng']
	lat=compute_dynamic['lat']
	alarm_time=compute_dynamic['serv_receive']
	efence=eval(efence)
	for efence_name in efence:
		efence_detail=efence[efence_name]
		alarm_kind=efence_detail['alarm_kind']
		s_time=efence_detail['s_time']
		e_time=efence_detail['e_time']
		last_inout=efence_detail.get('in_out_status','')
		# if efence_detail.get('efence_status')!=None:
		# 	last_inout=efence_detail['efence_status']['in_out_status']
		
		#围栏生效时间判断
		now=int(time.time())
		has_st=''
		has_et=''
		if len(s_time)==19:
			s_time=int(time.mktime(time.strptime(s_time, "%Y-%m-%d %H:%M:%S")))
			has_st='yes'
		if len(e_time)==19:
			e_time=int(time.mktime(time.strptime(e_time, "%Y-%m-%d %H:%M:%S")))
			has_et='yes'

		efence_judge='yes'

		if has_st=='yes' and has_et=='yes':
			if now<s_time or now>e_time:
				efence_judge='no'

		elif has_st=='' and has_et=='yes':
			if now>e_time:
				efence_judge='no'
		elif has_st=='yes' and has_et=='':
			if now<s_time:
				efence_judge='no'
		
		if efence_judge=='yes':
			#围栏的内容和围栏类型
			efence_data=efence_detail['content']
			efence_kind=efence_detail['kind']
			#获取最新设备存储的围栏报警信息
			efence_point=[lng,lat]
			judge_result=last_inout
			this_status='0'

			#多边形围栏判断,加入try，防止计算失败导致奔溃
			if efence_kind=='polygon' or efence_kind=='rectangle':
				try:
					judge_result=poly_efence(efence_point,efence_data)
				except:
					print('polygon_error',efence_point,efence_data)

			#圆形围栏判断，防止计算失败导致奔溃
			elif efence_kind=='circle':
				try:
					circle_center=[efence_data[1]['lng'],efence_data[1]['lat']]
					compute_data=get_distance(efence_point,circle_center)
					judge_result=(compute_data)*1000<efence_data[0]
				except:
					print('circle_error',efence_point,efence_data)

			# 对围栏计算结果进行转换
			title=''
			if judge_result==True:
				judge_result='in'
				title='进入'
			elif judge_result==False:
				judge_result='out'
				title='超出'

			# print(title,judge_result)
			# 判断上次围栏判断情况，如果初次计算，或者进出状态发生变化，进行进一步判断处理
			if last_inout=='0' or last_inout=='' or judge_result!=last_inout:
				# 进出判断的结果和进出判断报警一致的,提交报警信息至redis，
				alarm_content=title+','+efence_name

				# 看是否存在报警记录，有的话，报警记录做结束处理，添加结束时间。
				alarm_status=efence_detail.get('alarm_status','0')
				if alarm_status!='0':
					alarm_name=alarm_status[0]
					s_alarm_time=alarm_status[1]
					alarm_to_db={'name':alarm_name,'device_id':device_id,
								 'location':alarm_loca,'content':efence_name+'，围栏报警结束','kind':efence_kind,'handle':'auto_end',
								 's_time':s_alarm_time,'e_time':alarm_time,'remark':'0','owner':owner}
					msg_to_db={'table_name':'car_app_alarm','handle_kind':'update','content':alarm_to_db}
					mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
					this_status={'in_out_status':judge_result,'alarm_status':'0'}

				if judge_result==alarm_kind or alarm_kind=='all':
					alarm_name=device_id+efence_name+alarm_time
					alarm_to_db={'name':alarm_name,'device_id':device_id,
								 'location':alarm_loca,'content':alarm_content,'kind':efence_kind,'handle':'no',
								 's_time':alarm_time,'e_time':'0','remark':'0','owner':owner}
					# 更新围栏的报警状态信息，efence_status内容
					this_status={'in_out_status':judge_result,'alarm_status':[alarm_name,alarm_time,'0',[lng,lat]]}

					# 数据提交到redis
					msg_to_db={'table_name':'car_app_alarm','handle_kind':'add','content':alarm_to_db}
					mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
					
			if this_status!='0':
				efence[efence_name].update(this_status)

	efence=str(efence)
	# print('efe_res==>',efence)

	return efence


def polyline_compute(compute_dynamic,all_line,dev_line,owner):
	# print('START-----------------------------------POLYLINE')
	# 获取绑定的围栏信息
	dev_line=eval(dev_line)
	line_name=dev_line['name']
	line_detail=all_line[line_name]
	alarm_loca=compute_dynamic['location']
	#生效时间判断
	s_time=line_detail['s_time']
	e_time=line_detail['e_time']
	
	now=int(time.time())
	has_st=''
	has_et=''
	if len(s_time)==19:
		s_time=int(time.mktime(time.strptime(s_time, "%Y-%m-%d %H:%M:%S")))
		has_st='yes'
	if len(e_time)==19:
		e_time=int(time.mktime(time.strptime(e_time, "%Y-%m-%d %H:%M:%S")))
		has_et='yes'

	time_judge='yes'

	if has_st=='yes' and has_et=='yes':
		if now<s_time or now>e_time:
			efence_judge='no'

	elif has_st=='' and has_et=='yes':
		if now>e_time:
			efence_judge='no'
	elif has_st=='yes' and has_et=='':
		if now<s_time:
			efence_judge='no'
	
	if time_judge=='yes':

		device_id=compute_dynamic['device_id']
		alarm_loca=compute_dynamic['location']
		alarm_time=compute_dynamic['serv_receive']
		lng=compute_dynamic['lng']
		lat=compute_dynamic['lat']


		points=eval(line_detail['content'])
		min_dis=get_min_distance(points,lng,lat)
		# print('min_dis====>',min_dis)
		judge=min_dis*1000>500
		
		last_status=dev_line.get('alarm_status','0')

		if judge:
			deviate_pt=dev_line.get('deviate_pt',[])
			if len(deviate_pt)>21:
				deviate_pt[-1]=[lng,lat]
			else:
				deviate_pt.append([lng,lat])

			dev_line['deviate_pt']=deviate_pt
			
			# print('deviate_pt====>',len(deviate_pt))
			if len(deviate_pt)>20 and last_status=='0':
				
				alarm_content='偏移路线,'+line_name+','+str(round(min_dis)*1000)+'米'
				alarm_name=device_id+'polyline_alarm'+alarm_time
				alarm_to_db={'name':alarm_name,'device_id':device_id,
							 'location':alarm_loca,'content':alarm_content,'kind':'polyline','handle':'no',
							 's_time':alarm_time,'e_time':'0','remark':'0','owner':owner}
				# 更新报警状态信息，
				dev_line['alarm_status']=[alarm_name,alarm_time]

				# 数据提交到redis
				msg_to_db={'table_name':'car_app_alarm','handle_kind':'add','content':alarm_to_db}
				mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
		else:
			if last_status!='0':
				alarm_content='偏移路线,'+line_name+','+str(round(min_dis)*1000)+'米'
				alarm_name=last_status[0]
				s_alarm_time=last_status[1]
				alarm_to_db={'name':alarm_name,'device_id':device_id,
							 'location':alarm_loca,'content':alarm_content,'kind':'polyline','handle':'auto_end',
							 's_time':s_alarm_time,'e_time':alarm_time,'remark':'0','owner':owner}
				msg_to_db={'table_name':'car_app_alarm','handle_kind':'update','content':alarm_to_db}
				mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
				dev_line['alarm_status']='0'
				dev_line['deviate_pt']=[]

	# print('dev_line==>',dev_line)
	return str(dev_line)


#停留超时报警()
#一、上次停留状态信息，可能的状态
# 初始状态（新设备的第一条数据）,
# 开始停留
# 持续停留
# 开始移动（停留结束）
# 持续移动
#判断为初始状态，赋初值，不为初始状态，取上一次判断的状态结果，结果的status为0，上一次判读信息清零，则赋初值，开始新一轮判断计算，即对比本次的信息，判读出停留数据。

#二、本次的接收到的信息，对比上次停留，判断得出本次信息
# 停留，速度为0，
# 移动，速度不为0，


def stop_alarm_compute(compute_dynamic,stop_alarm,stop_rate,owner):
	device_id=compute_dynamic['device_id']
	serv_receive=compute_dynamic['serv_receive']
	alarm_loca=compute_dynamic['location']
	idling_time=stop_alarm.get('idling_time')
	last_status=stop_alarm.get('alarm_status','0')
	this_status=''
	# 给判断结果赋初值，防止速度值乱设置，无法进行比较，导致系统奔溃
	judge_result=''
	# 判断停留情况
	if idling_time==0:
		judge_result='move'
	else:
		judge_result=idling_time>stop_rate
		if judge_result==True:
			judge_result='over_stop'
		elif judge_result==False:
			judge_result='normal'

	# 如果是停留超时，判断为从正常进入超时，提交报警数据
	if last_status=='0' and judge_result=='over_stop':
		
		#定义报警数据字典
		alarm_name=device_id+'stop_alarm'+serv_receive
		alarm_to_db={'name':alarm_name,'device_id':device_id,
					 'location':alarm_loca,'content':'停留限制：'+str(round(stop_rate/60))+',实际停留：'+str(round(idling_time/60)),'kind':'stop_alarm','handle':'no',
					 's_time':serv_receive,'e_time':'0','remark':'0','owner':owner}
		# 更新停留的报警状态信息，efence_status内容
		stop_alarm['alarm_status']=[alarm_name,serv_receive,idling_time]
		# 数据提交到redis
		msg_to_db={'table_name':'car_app_alarm','handle_kind':'add','content':alarm_to_db}
		mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))

	# 进入移动状态,并且之前有报警状态，结束停留报警
	elif judge_result=='move' and last_status!='0':
		alarm_name=last_status[0]
		astime=last_status[1]
		last_idling=last_status[2]
		# 计算总的停留时间
		if len(astime)==19 and len(serv_receive)==19:
			astime=int(time.mktime(time.strptime(astime, "%Y-%m-%d %H:%M:%S")))
			serv_receive=int(time.mktime(time.strptime(serv_receive, "%Y-%m-%d %H:%M:%S")))
			idling_time=last_idling+serv_receive-astime

		alarm_to_db={'name':alarm_name,'device_id':device_id,
					 'location':alarm_loca,'content':'停留限制：'+str(round(stop_rate/60))+',实际停留：'+str(round(idling_time/60)),'kind':'stop_alarm','handle':'auto_end',
					 's_time':astime,'e_time':serv_receive,'remark':'0','owner':owner}
		msg_to_db={'table_name':'car_app_alarm','handle_kind':'update','content':alarm_to_db}
		mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))

		stop_alarm['alarm_status']='0'

	return stop_alarm

# 超速报警
def speed_alarm_compute(compute_dynamic,speed_limit,speed_alarm,owner):
	device_id=compute_dynamic['device_id']
	serv_receive=compute_dynamic['serv_receive']
	alarm_loca=compute_dynamic['location']
	current_speed=float(compute_dynamic['speed'])
	last_status=speed_alarm.get('alarm_status','0')
	# 给判断结果赋初值，防止速度值乱设置，无法进行比较，导致系统奔溃
	judge_result=''

	judge_result=current_speed>speed_limit
	if judge_result==True:
		judge_result='over_speed'
	elif judge_result==False:
		judge_result='normal'

	# 如果是超速，判断为从正常进入超速，提交报警数据
	if last_status=='0' and judge_result=='over_speed':
		
		#定义报警数据字典
		alarm_name=device_id+'speed_alarm'+serv_receive
		alarm_to_db={'name':alarm_name,'device_id':device_id,
					 'location':alarm_loca,'content':'限速：'+str(speed_limit)+',当前速度：'+str(current_speed),'kind':'speed_alarm','handle':'no',
					 's_time':serv_receive,'e_time':'0','remark':'0','owner':owner}
		# 更新停留的报警状态信息，efence_status内容
		speed_alarm['alarm_status']=[alarm_name,serv_receive,current_speed]
		# 数据提交到redis
		msg_to_db={'table_name':'car_app_alarm','handle_kind':'add','content':alarm_to_db}
		mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))

	# 进入停留状态,并且之前有报警状态，结束超速报警
	elif judge_result=='normal' and last_status!='0':
		alarm_name=last_status[0]
		astime=last_status[1]
		last_speed=last_status[2]
		alarm_to_db={'name':alarm_name,'device_id':device_id,
					 'location':alarm_loca,'content':'限速：'+str(speed_limit)+',速度：'+str(last_speed)+'-'+str(current_speed),'kind':'speed_alarm','handle':'auto_end',
					 's_time':astime,'e_time':serv_receive,'remark':'0','owner':owner}
		msg_to_db={'table_name':'car_app_alarm','handle_kind':'update','content':alarm_to_db}
		mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))

		speed_alarm['alarm_status']='0'
		# print('speed_alarm==>',speed_alarm)
	return speed_alarm



# 其他报警记录
def other_alarm(compute_dynamic,alarm_mesg,owner,all_alarm_status):
	device_id=compute_dynamic['device_id']
	location=compute_dynamic['location']
	alarm_time=compute_dynamic['serv_receive']
	alarm_loca=compute_dynamic['location']
	for x in alarm_mesg:
		alarm_status=all_alarm_status.get(x,'0')
		if alarm_status=='0':
			alarm_name=device_id+x+alarm_time
			alarm_to_db={'name':alarm_name,'device_id':device_id,
						 'location':location,'content':x,'handle':'no',
						 's_time':alarm_time,'e_time':'0','remark':'0','kind':x,'owner':owner}
			# 数据提交到redis
			msg_to_db={'table_name':'car_app_alarm','handle_kind':'add','content':alarm_to_db}
			mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
			all_alarm_status[x]=[alarm_name,alarm_time]

	for x in all_alarm_status:
		alarm_status=all_alarm_status.get(x)
		if x not in alarm_mesg and alarm_status!='0':
			alarm_name=alarm_status[0]
			astime=alarm_status[1]
			alarm_to_db={'name':alarm_name,'device_id':device_id,
						 'location':alarm_loca,'content':x,'kind':x,'handle':'auto_end',
						 's_time':astime,'e_time':alarm_time,'remark':'0','owner':owner}
			msg_to_db={'table_name':'car_app_alarm','handle_kind':'update','content':alarm_to_db}
			mesg_redis.rpush('car_alarm_to_db',str(msg_to_db))
			all_alarm_status[x]='0'

	return all_alarm_status



def get_min_distance(points,lng,lat):
	points=np.array(points)
	t_lng=lng* pi / 180.0
	t_lat=lat* pi / 180.0
	
	radLat1 = points[:,1:2]*pi/180
	radLat2 = 26.09908511* pi / 180.0

	lng_b=points[:,0:1]*pi/180-t_lng
	lat_a=points[:,1:2]*pi/180-t_lat
	distance_list=2*np.arcsin(np.sqrt(np.power(np.sin(lat_a/2),2)+np.cos(radLat1)*cos(radLat2)*np.power(np.sin(lng_b/2),2)))*6378.137

	return distance_list.min()


#点位置是否在多边形里面的的判断函数

def poly_efence(p,efence_data):

	efence_data=efence_data
	efencepoly=[]
	efencepolys=[]
	hard_point=[]
	hard_points=[]
	for x in efence_data:
		hard_point.append(x['lng'])
		hard_point.append(x['lat'])
		if len(hard_point)==2:
			efencepoly.append(hard_point)
			hard_point=[]
	for x in range(len(efencepoly)-1):
		hard_points.append(efencepoly[x])
		hard_points.append(efencepoly[x+1])
		efencepolys.append(hard_points)
		hard_points=[]
		if x==len(efencepoly)-2:
			hard_points.append(efencepoly[x])
			hard_points.append(efencepoly[0])
			efencepolys.append(hard_points)
	#print('efencepolys:',efencepolys)
	def isRayIntersectsSegment(poi,s_poi,e_poi): #[x,y] [lng,lat]
		#输入：判断点，边起点，边终点，都是[lng,lat]格式数组
		if s_poi[1]==e_poi[1]: #排除与射线平行、重合，线段首尾端点重合的情况
			return False
		if s_poi[1]>poi[1] and e_poi[1]>poi[1]: #线段在射线上边
			return False
		if s_poi[1]<poi[1] and e_poi[1]<poi[1]: #线段在射线下边
			return False
		if s_poi[1]==poi[1] and e_poi[1]>poi[1]: #交点为下端点，对应spoint
			return False
		if e_poi[1]==poi[1] and s_poi[1]>poi[1]: #交点为下端点，对应epoint
			return False
		if s_poi[0]<poi[0] and e_poi[1]<poi[1]: #线段在射线左边
			return False

		xseg=e_poi[0]-(e_poi[0]-s_poi[0])*(e_poi[1]-poi[1])/(e_poi[1]-s_poi[1]) #求交
		if xseg<poi[0]: #交点在射线起点的左侧
			return False
		return True  #排除上述情况之后


	def isPoiWithinPoly(poi,poly):
		#输入：点，多边形三维数组
		#poly=[[[x1,y1],[x2,y2],……,[xn,yn],[x1,y1]],[[w1,t1],……[wk,tk]]] 三维数组

		#可以先判断点是否在外包矩形内 
		#if not isPoiWithinBox(poi,mbr=[[0,0],[180,90]]): return False
		#但算最小外包矩形本身需要循环边，会造成开销，本处略去
		sinsc=0 #交点个数
		for epoly in poly: #循环每条边的曲线->each polygon 是二维数组[[x1,y1],…[xn,yn]]
			for i in range(len(epoly)-1): #[0,len-1]
				s_poi=epoly[i]
				e_poi=epoly[i+1]
				if isRayIntersectsSegment(poi,s_poi,e_poi):
					sinsc+=1 #有交点就加1

		return True if sinsc%2==1 else  False
	#print(isPoiWithinPoly(p,efencepolys))
	result=isPoiWithinPoly(p,efencepolys)

	return result

#两点经纬度计算距离函数
def get_distance(point1,point2):
	lat1=point1[1]
	lat2=point2[1]
	lng1=point1[0]
	lng2=point2[0]

	EARTH_REDIUS = 6378.137

	def rad(d):
		return d * pi / 180.0

	radLat1 = rad(lat1)
	radLat2 = rad(lat2)
	lat_a = radLat1 - radLat2
	lng_b = rad(lng1) - rad(lng2)
	result_s = 2 * math.asin(math.sqrt(math.pow(sin(lat_a/2), 2) + cos(radLat1) * cos(radLat2) * math.pow(sin(lng_b/2), 2)))
	result_s = result_s * EARTH_REDIUS

	return result_s

#wgs84坐标系转换成百度坐标系计算函数
def wgs84_bdmap(wpoint):

	pi = 3.14159265358979324;
	a = 6378245.0;
	ee = 0.00669342162296594323;
	x_pi = 3.14159265358979324 * 3000.0 / 180.0;

	def wgs2bd(lat, lon): 
		   _wgs2gcj = wgs2gcj(lat, lon);
		   _gcj2bd = gcj2bd(_wgs2gcj[0], _wgs2gcj[1]);
		   return _gcj2bd;
	

	def gcj2bd(lat, lon):
		   x = lon
		   y = lat
		   z = math.sqrt(x * x + y * y) + 0.00002 * math.sin(y * x_pi);
		   theta = math.atan2(y, x) + 0.000003 * math.cos(x * x_pi);
		   bd_lon = z * math.cos(theta) + 0.0065;
		   bd_lat = z * math.sin(theta) + 0.006;
		   return [ bd_lat, bd_lon ];
	

	def bd2gcj(lat, lon): 
		   x = lon - 0.0065
		   y = lat - 0.006
		   z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi);
		   theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi);
		   gg_lon = z * math.cos(theta);
		   gg_lat = z * math.sin(theta);
		   return [ gg_lat, gg_lon ];
	

	def wgs2gcj(lat, lon): 
		   dLat = transformLat(lon - 105.0, lat - 35.0);
		   dLon = transformLon(lon - 105.0, lat - 35.0);
		   radLat = lat / 180.0 * pi;
		   magic = math.sin(radLat);
		   magic = 1 - ee * magic * magic;
		   sqrtMagic = math.sqrt(magic);
		   dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
		   dLon = (dLon * 180.0) / (a / sqrtMagic * math.cos(radLat) * pi);
		   mgLat = lat + dLat;
		   mgLon = lon + dLon;
		   return [ mgLat, mgLon ];
	

	def transformLat(lat, lon): 
		   ret = -100.0 + 2.0 * lat + 3.0 * lon + 0.2 * lon * lon + 0.1 * lat * lon + 0.2 * math.sqrt(abs(lat));
		   ret += (20.0 * math.sin(6.0 * lat * pi) + 20.0 * math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
		   ret += (20.0 * math.sin(lon * pi) + 40.0 * math.sin(lon / 3.0 * pi)) * 2.0 / 3.0;
		   ret += (160.0 * math.sin(lon / 12.0 * pi) + 320 * math.sin(lon * pi  / 30.0)) * 2.0 / 3.0;
		   return ret;
	

	def transformLon(lat, lon): 
		   ret = 300.0 + lat + 2.0 * lon + 0.1 * lat * lat + 0.1 * lat * lon + 0.1 * math.sqrt(abs(lat));
		   ret += (20.0 * math.sin(6.0 * lat * pi) + 20.0 * math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
		   ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0;
		   ret += (150.0 * math.sin(lat / 12.0 * pi) + 300.0 * math.sin(lat / 30.0 * pi)) * 2.0 / 3.0;
		   return ret;
	wgs2bd=wgs2bd(wpoint[1],wpoint[0])
	wgs2bd.reverse()
	return wgs2bd